1、随着下拉,view发生位移,松开回弹到原来的位置
2、内部的listview可以正常的上下滑动
3、listview滑到顶部的时候,继续下拉,则是拉动整个外部view,并且松开回弹
这3个需求就会造成事件冲突,那么处理方式就是:listview不是初始状态就是listview自己处理事件,listview还原到了初始状态,外部view处理下拉回弹事件。
需求一个一个的实现,首先第一个下拉回弹
因为里面还要套一个listview,所以我们自定义一个view继承自viewGroup,这里选择的是LinearLayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.aidebar.demo;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.widget.LinearLayout;public class MyView extends LinearLayout { private int startY; private int moveY; private int diffY; public MyView (Context context) { super (context); } public MyView (Context context, AttributeSet attrs) { super (context, attrs); } public MyView (Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); } @Override public boolean onTouchEvent (MotionEvent event) { int y = (int ) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startY = y; break ; case MotionEvent.ACTION_MOVE: moveY = y; Log.d("myview" , "moveY=" +moveY+"|startY=" +startY); if ((moveY - startY) > 0 ) { layout(getLeft(), getTop() + (moveY - startY), getRight(), getBottom() + (moveY - startY)); diffY += (moveY - startY); } break ; case MotionEvent.ACTION_UP: layout(getLeft(), getTop() - diffY, getRight(), getBottom() - diffY); diffY = 0 ; break ; } return true ; } }
OK,实现第二条需求,让内部listview可以滑动,要让子view可以接受到MotionEvent,首先我们自己就不能拦截,那么重写onInterceptTouchEvent()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public boolean onInterceptTouchEvent (MotionEvent ev) { boolean isIntercept=false ; int y = (int ) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startY = y; break ; case MotionEvent.ACTION_MOVE: moveY = y; if ((moveY - startY) > 0 ) { isIntercept = true ; }else { isIntercept = false ; } break ; case MotionEvent.ACTION_UP: break ; } return isIntercept; }
好,拦截方法写完了,但这样的话,所有向下滑动都被我们拦截了,listview就不能向下滑了。
但这是listview的事情,应该由listview来做判断,什么时候拦截什么时候不拦截。
自定义一个MyListView,继承自ListView,并重写onTouchEvent()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public boolean onTouchEvent (MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true ); break ; case MotionEvent.ACTION_MOVE: if (getScrollY() == 0 ) { getParent().requestDisallowInterceptTouchEvent(false ); }else { getParent().requestDisallowInterceptTouchEvent(true ); } break ; } return super .onTouchEvent(ev); }
这么写会发现无效,因为listview不想scrollview,它没有重写getScrollY()方法,直接调用的是父类view的方法,返回值永远是0. 没写也没关系,我们自己写
1 2 3 4 5 6 7 8 9 10 11 public int getScrollY1 () { View v = getChildAt(0 ); if (v == null ) { return 0 ; } int firstVisiblePosition = getFirstVisiblePosition(); int top = v.getTop(); return -top + firstVisiblePosition * v.getHeight() ; }
将上面的getScrollY()
替换成getScrollY1()
即可。
OK,listview可以正常滑动了,第二条需求完成
大功告成?太年轻了。。
你会发现可以下拉回弹,listview可以上下滑动并且下拉回弹,但是!
你先把listview往上滑一下,松手,然后再下拉试试
会发现在临界状态下,外部的view突然往下移动了一大截。
为什么不松手的情况下,listview可以上下滑动,滑到顶了外部view可以正常下拉并回弹,而先滑动一次listview就不行了呢?
因为我们在外部view的onInterceptTouchEvent()
里获取到了startY,所以当listview复原的时候的moveY和这个startY是相等的,外部view就可以正常的下拉回弹。
而先滑动一次listview后,再次点击滑动,获取到的是一个新的startY,而此时你要把listview复原的moveY是大于startY的,所以listview滑到顶的时候再下拉,布局会突然下降一截。
知道原因就好解决了,在listview处理滑动事件的时候,复原的时候,将moveY的值给外部view的startY赋值就行了呗!
怎么传值,请看RxBus工具类 ,这是用rxjava写的一个取代EventBus的工具。
在MyView中初始化的时候注册一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public MyView (Context context, AttributeSet attrs) { super (context, attrs); init(); } public MyView (Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); init(); } private void init () { RxBus.getInstance().toObservable(Integer.class,"startY" ) .subscribe(new RxBusSubscriber<Integer>() { @Override public void receive (Integer data) { startY = data; } }); }
在MyListView中加一句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public boolean onTouchEvent (MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true ); break ; case MotionEvent.ACTION_MOVE: if (getScrollY1() == 0 ) { RxBus.getInstance().send((int )ev.getY(),"startY" ); getParent().requestDisallowInterceptTouchEvent(false ); }else { getParent().requestDisallowInterceptTouchEvent(true ); } break ; }
OK,全部搞定,收工~